﻿using System;
using System.IO;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;

namespace LibraryGuard.Guards
{
	/// <summary>
	/// Searches for unwanted files and deletes them.
	/// </summary>
	public class UnwantedFilesGuard
		: IGuard
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("UnwantedFiles");
		#endregion

		#region Fields
		private readonly IConfiguration BaseConfig = null;
		private readonly Configuration.UnwantedFilesGuard Config = null;
		#endregion

		#region IGuard Members
		/// <inheritdoc />
		public bool OnlySpecificExtensions
		{
			get { return false; }
		}

		/// <inheritdoc />
		public IActionList ActionList { get; set; }

		/// <inheritdoc />
		public void Run()
		{
			this.BaseConfig.Directories.ForEach(d => this.ActionList.Add(new GuardDirectory(d, this)));
		}

		/// <inheritdoc />
		public void FileSystemEvent(FileSystemEventArgs args, string baseDirectory)
		{
			if (args.ChangeType != WatcherChangeTypes.Deleted)
			{
				Logger.Debug("File/Directory '{0}' created/changed/renamed, guard", args.FullPath);
				FileSystemInfo fsi = null;
				if (File.Exists(args.FullPath))
					fsi = new FileInfo(args.FullPath);
				else
					fsi = new DirectoryInfo(args.FullPath);
				this.ActionList.Remove<DeleteEntry>(fsi, Comparers.FSI);
				this.ActionList.Add(new CheckEntry(fsi, this.Config), Comparers.FSI);
			}
		}
		#endregion

		#region Constructor
		public UnwantedFilesGuard(IConfiguration config, IGuardConfiguration guard)
		{
			Validate.Debug(() => config, v => v.NotNull());
			Validate.Debug(() => guard, v => v.NotNull());

			this.BaseConfig = config;
			this.Config = (Configuration.UnwantedFilesGuard)guard;

			Logger.Info("Unwanted files guard enabled");
			if (this.Config.Hidden)
				Logger.Debug("Hidden files will be deleted");
			if (this.Config.Extensions.Count > 0)
				Logger.Debug("Files with these extensions will be deleted: " + string.Join(", ", this.Config.Extensions));
			if (this.Config.Exceptions.Count > 0)
				Logger.Debug("These files will be ignored: " + string.Join(", ", this.Config.Exceptions));
		}
		#endregion

		#region Actions
		/// <summary>
		/// Guards directory - lists files and subdirectories.
		/// </summary>
		private sealed class GuardDirectory
			: IActionInfo
		{
			#region Private fields
			private readonly DirectoryInfo Directory = null;
			private readonly UnwantedFilesGuard Parent = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.Directory; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return false; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				this.Directory.Refresh();
				if (!this.Directory.Exists)
				{
					Logger.Info("Directory '{0}' doesn't exist, ignoring", this.Directory.FullName);
					return true;
				}

				Logger.Trace("Searching directory '{0}'", this.Directory.FullName);
				try
				{
					var entries = this.Directory.GetFileSystemInfos("*", SearchOption.AllDirectories);
					Array.ForEach(entries, e => actionList.Replace(new CheckEntry(e, this.Parent.Config), Comparers.FSI));
				}
				catch (Exception ex)
				{
					Logger.WarnException("Cannot guard directory '{0}'".FormatWith(this.Directory.FullName), ex);
					return false;
				}
				return true;
			}
			#endregion

			#region Constructor
			public GuardDirectory(DirectoryInfo di, UnwantedFilesGuard parent)
			{
				this.Directory = di;
				this.Parent = parent;
			}
			#endregion
		}

		/// <summary>
		/// Checks FileSystemInfo.
		/// </summary>
		private sealed class CheckEntry
			: IActionInfo
		{
			#region Private fields
			private FileSystemInfo FSI = null;
			private Configuration.UnwantedFilesGuard Config = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.FSI; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return false; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				this.FSI.Refresh();
				if (!this.FSI.Exists)
				{
					Logger.Debug("File/Directory '{0}' doesn't exist, ignoring", this.FSI.FullName);
					return true;
				}

				Logger.Trace("Checking '{0}'", this.FSI.FullName);
				bool unwanted = false;
				if (this.Config.Hidden && this.FSI.Attributes.HasFlag(FileAttributes.Hidden))
				{
					unwanted = true;
				}
				else
				{
					var ext = this.FSI.Extension;
					if (ext.Length > 0)
						ext = ext.Remove(0, 1);
					unwanted = this.Config.Extensions.Contains(ext);
				}
				if (unwanted && !this.Config.Exceptions.Contains(this.FSI.Name))
					actionList.Add(new DeleteEntry(this.FSI), Comparers.FSI);

				return true;
			}
			#endregion

			#region Constructor
			public CheckEntry(FileSystemInfo fsi, Configuration.UnwantedFilesGuard config)
			{
				this.FSI = fsi;
				this.Config = config;
			}
			#endregion
		}

		/// <summary>
		/// Edge action - deletes unwanted file/directory.
		/// </summary>
		private sealed class DeleteEntry
			: IActionInfo
		{
			#region Private fields
			private readonly FileSystemInfo FSI = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.FSI; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return true; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				this.FSI.Refresh();
				if (!this.FSI.Exists)
				{
					Logger.Debug("File/Directory '{0}' doesn't exist, ignoring", this.FSI.FullName);
					return true;
				}

				try
				{
					if (this.FSI is DirectoryInfo)
						((DirectoryInfo)this.FSI).Delete(true);
					else
						this.FSI.Delete();
					Logger.Info("'{0}' deleted", this.FSI.FullName);
				}
				catch (Exception ex)
				{
					Logger.WarnException("Cannot delete '{0}'".FormatWith(this.FSI.FullName), ex);
					return false;
				}
				return true;
			}
			#endregion

			#region Constructor
			public DeleteEntry(FileSystemInfo fsi)
			{
				this.FSI = fsi;
			}
			#endregion

			#region ToString
			public override string ToString()
			{
				return "Delete file '{0}'".FormatWith(this.FSI.FullName);
			}
			#endregion
		}
		#endregion
	}
}
